﻿using System;
using System.Collections.Generic;
using System.Management.Automation;

using CrystalDecisions.Enterprise;

namespace BO.Management.PowerShell
{
    /// <summary>
    /// Base Cmdlet to manage BO. This class offers infrastructure to connect to BO
    /// </summary>
    public abstract class BaseBOEObject<T, U> : BaseBOECmdlet
        where T : BaseObjectProvider<U>
        where U : CrystalDecisions.Enterprise.InfoObject
    {

        #region "Private members"
        private System.Diagnostics.Stopwatch _watch;

        /// <summary>
        /// Swithc parameter to use recursivity with ParentId
        /// </summary>
        private Boolean _recurse;

        /// <summary>
        /// Depth to browse inside a folder. Default 0 is unlimited Depth
        /// </summary>
        private Int32 _depth = 0;

        /// <summary>
        /// search pattern of hyperlink
        /// </summary>
        private String _pattern;

        /// <summary>
        /// ParentId to start the search
        /// </summary>
        private Int32 _parentId;

        /// <summary>
        /// Parent Folder to start the search
        /// </summary>
        private CrystalDecisions.Enterprise.Desktop.Folder _parentFolder;

        private List<Int32> _outputObjectIds = new List<int>();
        #endregion


        #region "Public properties"

        [Parameter(Mandatory = false, ValueFromPipeline = true)]
        public Int32? ID { get; set; }

        [Parameter(Mandatory = false, ValueFromPipeline = false)]
        public String Name { get; set; }

        [Parameter(Mandatory = false, ValueFromPipeline = true)]
        public Int32? ParentId { get; set; }

        [Parameter(Mandatory = false, ValueFromPipeline = true)]
        public String FullPath { get; set; }

        [Parameter(Mandatory = false, ValueFromPipeline = true)]
        public String Cuid { get; set; }

        [Parameter(Mandatory = false, ValueFromPipeline = false)]
        public String ParentCuid { get; set; }

        [Parameter(Mandatory = false, ValueFromPipeline = true)]
        public DateTime? Created { get; set; }

        [Parameter(Mandatory = false, ValueFromPipeline = false)]
        public Int32? First { get; set; }


        [Parameter()]
        public SwitchParameter Recurse { get; set; }

        [Parameter()]
        public Int32 Depth
        {
            get { return _depth; }
            set { _depth = value; }
        }

        #endregion

        internal Boolean ThrowExceptionOnObjectNotFound
        {
            get;
            set;
        }


        internal List<Int32> OutputObjectIds
        {
            get { return _outputObjectIds; }
        }


        private void InternalWorkWithSingleObject(U boeObject)
        {

            //Add this objects ID in the list to know objects that will be treated by the cmdlet
            if (!this.OutputObjectIds.Contains(boeObject.ID))
            {
                this.OutputObjectIds.Add(boeObject.ID);
            }

            this.WorkWithSingleObject(boeObject);
        }


        public abstract T GetProvider();

        public abstract void WorkWithSingleObject(U boeObject);

        public abstract void WorkWithListObjects(List<U> boeObjects);

        //public U OutputObject { get; internal set; }
        //public List<U> OutputObjects { get; internal set; }

        internal T BoeProvider;

        /// <summary>
        /// Dispose the Event 
        /// </summary>
        protected override void EndProcessing()
        {
            //if (this.Folder != null) this.Folder.Dispose();
            if (BoeProvider != null) BoeProvider.Dispose();
            base.EndProcessing();
            _watch.Stop();
            WriteVerbose(String.Format("End {0} in {1}", this.GetType().Name, _watch.Elapsed));
        }

        protected override void BeginProcessing()
        {
            WriteVerbose(String.Format("Start {0}", this.GetType().Name));
            _watch = new System.Diagnostics.Stopwatch();
            _watch.Start();
            base.BeginProcessing();
        }

        /// <summary>
        /// Get the business Object provider for this type of object then, find the object according search parameters
        /// </summary>
        protected override void ProcessRecord()
        {
            base.ProcessRecord();
            BoeProvider = this.GetProvider();
            this.FindBoeObject();
        }

        /// <summary>
        /// find the object according search parameters :
        /// - If one object, the property to store object is OutputObject and the method called is workWithSingleObject
        /// - If many objects, the property to store object is OutputObjects and the method called is workWithListObjects
        /// </summary>
        protected virtual void FindBoeObject()
        {

            //Specific search for a Full Path
            if (!String.IsNullOrEmpty(this.FullPath))
            {
                this.ProcessFullPath(this.FullPath);
                return;
            }

            //Add the parameters to search with one or multiple criterias
            if (this.ID.HasValue)
            {
                BoeProvider.AddFilterToId(QueryBuilder.OperatorType.Equal, this.ID.Value);
            }

            if (!String.IsNullOrEmpty(this.Name))
            {
                if (this.Name.Contains("%"))
                    BoeProvider.AddFilterToName(QueryBuilder.OperatorType.Like, this.Name);
                else
                    BoeProvider.AddFilterToName(QueryBuilder.OperatorType.Equal, this.Name);

            }

            if (this.ParentId.HasValue)
            {
                BoeProvider.AddFilterToParent(QueryBuilder.OperatorType.Equal, this.ParentId.Value);
            }

            if (!String.IsNullOrEmpty(this.Cuid))
            {
                BoeProvider.AddFilterToCuid(this.Cuid);
            }

            if (this.Created.HasValue)
            {
                BoeProvider.AddFilterToCreationDate(QueryBuilder.OperatorType.GreatherOrEqual, this.Created.Value);
            }

            if (!String.IsNullOrEmpty(this.ParentCuid))
            {
                BoeProvider.AddFilterToParentCuid(this.ParentCuid);
            }

            if (this.First.HasValue)
            {
                BoeProvider.AddLimitOnTheQuery(this.First.Value);
            }


            if (!this.Recurse)
                this.ProcessQuery();
            else
                this.ProcessHierarchical(this.Recurse, this.Depth);
        }


        /// <summary>
        /// Process Object by Its id
        /// </summary>
        protected virtual U ProcessById(Int32 id)
        {
            WriteVerbose(String.Format("Get by Id {0}", id));
            U objectBO = BoeProvider.GetObject(id);
            if (objectBO != null)
            {
                WriteVerbose(String.Format("{0} Found with Id {1} and name {2}", BoeProvider.Kind, id, objectBO.Title));
                return objectBO;
            }
            else
            {
                WriteVerbose(String.Format("{0} not found with id {1}", BoeProvider.Kind, id));
                if (this.ThrowExceptionOnObjectNotFound)
                {
                    this.ThrowTerminatingErrorWithExitCode(BOErrorCode.ObjectNotFound, String.Format("{0} with Id {1} don't exist in the CMS", BoeProvider.Kind, this.ID));
                }
                //else
                //{
                //    Console.WriteLine(String.Format("{0} with Id {1} don't exist in the CMS", BoeProvider.Kind, this.ID));
                //}
                return null;
            }
        }


        /// <summary>
        /// Process Object by Its name
        /// </summary>
        protected virtual void ProcessQuery()
        {
            WriteVerbose(String.Format("Start Process query"));
            WriteVerbose(String.Format("Query : {0}", BoeProvider.QueryBuilder.GetQuery()));
            System.Action<U> workwithObject = w => this.InternalWorkWithSingleObject(w);

            Int32 result = BoeProvider.GetObject(workwithObject);
            if (result > 0)
                WriteVerbose(String.Format("Found {0} objects of type {1} with these filters {2}", result, BoeProvider.Kind, BoeProvider.QueryBuilder.GetFilter()));
            else
            {
                WriteVerbose(String.Format("Query return no objects of type {0} in the CMS for these filters {1}", BoeProvider.Kind, BoeProvider.QueryBuilder.GetFilter()));
                if (this.ThrowExceptionOnObjectNotFound)
                {
                    this.ThrowTerminatingErrorWithExitCode(BOErrorCode.ObjectNotFound, String.Format("Query return no objects of type {0} in the CMS for these filters {1}", BoeProvider.Kind, BoeProvider.QueryBuilder.GetFilter()));
                }
                //else
                //{
                //    Console.WriteLine(String.Format("Query return no objects of type {0} in the CMS for these filters {1}", BoeProvider.Kind, BoeProvider.QueryBuilder.GetFilter()));
                //}
            }
            WriteVerbose(String.Format("End Process query"));
        }


        /// <summary>
        /// Process Hierarchical
        /// </summary>
        protected virtual void ProcessHierarchical(Boolean recurse, Int32 depth)
        {
            this.ProcessHierarchical(BoeProvider, recurse, depth);
        }


        /// <summary>
        /// Process Hierarchical
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="recurse"></param>
        /// <param name="depth"></param>
        internal void ProcessHierarchical(T provider, Boolean recurse, Int32 depth)
        {
            WriteVerbose(String.Format("Start Process Hierarchical query with a recurse depth of {0}", this.Depth));
            WriteVerbose(String.Format("Query : {0}", provider.QueryBuilder.GetQuery()));
            System.Action<U> workwithObject = w => this.InternalWorkWithSingleObject(w);

            Int32 result = provider.GetObjectsHierachical(workwithObject, this.Recurse, this.Depth);
            if (result > 0)
                WriteVerbose(String.Format("Found {0} objects of type {1}", result, provider.Kind));
            else
            {

                WriteVerbose(String.Format("Query return no objects of type {0} in the CMS", provider.Kind));
                if (this.ThrowExceptionOnObjectNotFound)
                {
                    this.ThrowTerminatingErrorWithExitCode(BOErrorCode.ObjectNotFound, String.Format("Query return no objects of type {0} in the CMS", BoeProvider.Kind));
                }
                //else
                //{
                //    Console.WriteLine(String.Format("Query return no objects of type {0} in the CMS", provider.Kind));
                //}

            }
            WriteVerbose(String.Format("End Process Hierarchical query"));

        }

        /// <summary>
        /// Process Object from Its Full Path
        /// </summary>
        protected virtual void ProcessFullPath(String path)
        {
            WriteVerbose(String.Format("Get from Full Path {0}", path));

            Boolean isWildCard = false;
            InfoObject foundObject = BoeProvider.GetObjectFromPath(path, out isWildCard);

            if (foundObject == null)
            {
                WriteVerbose(String.Format("No Path {0} exist in the CMS", path));
                if (this.ThrowExceptionOnObjectNotFound)
                {
                    this.ThrowTerminatingError(new ErrorRecord(new Exception("InputObject is null. It hasn't been found in the CMS"),
                                                "NODATA", ErrorCategory.ObjectNotFound, null));
                }
                //else
                //{
                //    Console.WriteLine(String.Format("No Path {0} exist in the CMS", path));
                //}
            }
            else
            {
                if (isWildCard)
                {
                    WriteVerbose(String.Format("Find full path with wildcard from folder {0}", foundObject.Title));

                    using (T provider = this.GetProvider())
                    {
                        provider.AddFilterToParent(QueryBuilder.OperatorType.Equal, foundObject.ID);
                        this.ProcessHierarchical(provider, this.Recurse, this.Depth);
                    }
                }
                else
                {
                    WriteVerbose(String.Format("Find object {0} of kind {1} from the full path", foundObject.Title, foundObject.Kind));
                    if (!(foundObject is U))
                    {
                        this.ThrowTerminatingError(new ErrorRecord(new Exception(String.Format("The object found from the path {0} is not type of {1}", path, BoeProvider.Kind)),
                                        "INVALIDTYPE", ErrorCategory.InvalidResult, foundObject));
                    }
                    else
                    {
                        this.InternalWorkWithSingleObject((U)foundObject);
                    }
                }
            }
        }

    }
}
